//GENERAL_OPTIONS
//GENERAL HELPERS

(function (that) {

	//local Link to Global-Object or throw Exception
	if(!that){
		throw "Global object missing, bitchesss";
	}

	var options		= {
		inner_radius:			10,					//inner radius for the mouse-moves arcs
		outer_radius:			40,					//outer radius for the mouse-moves arcs
		base_gradient:			{					//color-stops for the coloring of the mouse-move arcs
			0.0: "rgb(171, 184, 211)",
			0.1: "rgb(92,178,74)",
			0.85: "rgb(252,225,88)",
			1.0: "rgb(242,46,46)"
		},
		y_max:					0,					//maximum y-value of the canvas (will be set and not stay at 0 hopefully)
		x_max:					0,					//maximum x-value of the canvas (will be set and not stay at 0 hopefully)
		max_opacity:			159,				//maximum opacity for arcs painted the overlay-maps
		min_opacity:			140,				//minimum opacity for arcs painted the overlay-maps
		main_container:			"container",		//identifier for the main_container of canvases, needed to set min-width
		iframe: 				"iframe",			//identifier for the iframe that displays the website
		max_value:				0,					//maximum value of a point (each event on a point iterates its value by one, happens on website itself not here)
		min_click_radius:		15,					//minimum radiues used for the clickmap-arcs displaying clicks
		canvas_padding:			110,				//total padding of the main container, to adjust canvas size
		canvas_width:			2000,				//canvas width
		canvas_height:			5000,				//canvas height
		click_map_alpha:		"0.55",				//alpha value for clickmap
		click_color: {								//colors for click events
			l:	"#D14836",
			c:	"#63A668",
			r:	"#3079ED"
		},
		attention_gradient:		{
			// 0.0: "rgb(200, 200, 200)",
			// 1.0: "rgb(255, 255, 255)"
			0.0: "rgb(171, 184, 211)",
			0.1: "rgb(92,178,74)",
			0.85: "rgb(252,225,88)",
			1.0: "rgb(242,46,46)"
		},
		attention_opacity:		100,				//opacity for attention map
		total_val:				0,					//total value aggregation (will be done later), for the attention-map
		attention_steps: 		250,				//steps for the attentionmap
		attention_step_values:	[]					//edge-values for the steps
	}



	//calculate double_radius
	, double_outer_radius =  options.outer_radius*2
	//variable to store iframe (iframe contains the webpage for which the maps are generated)
	, iframe = null;



	/*
	 * Create a 1x256 Pixels canvas, fill it with a Linear-Gradient based on options.base_gradient color stops
	 *
	 * @param context 		- the context of the calc_canvas used for any calculation in this module
	 * @return Array of the image Data of the gradient [0] -> red [1] -> green [2] -> blue [3] -> alpha
	 */
	function get_gradient (context, gradient) {

		var gradient			= gradient || options.base_gradient
		,	linear_gradient		= context.createLinearGradient(0,0,256,1)
		,	img_data;			//variable to save the img_data-Array

		//set the color-stops for the gradient
		that.each(gradient, function(val, key, obj){
			linear_gradient.addColorStop(key,val);
		});

		//fill canvas with the gradient
		context.fillStyle = linear_gradient;
		context.fillRect(0,0,256,1);

		img_data = context.getImageData(0,0,256,1).data;

		context.clearRect(0,0,256,1);

		return img_data;

	}

	/*
	 * Fill the heatmap_canvas based on the grayshades calculated on the calc_canvas and the linear gradient-colors
	 *
	 * @param context		- the 2d-Context of the canvas with the gray_shades
	 * @param x				- the maximum x value to draw the canvas to
	 * @param y				- the maximum y value to draw the canvas to
	 * @param paint_context	- the 2d-Context to put the colored image data to
	 * @return void
	 */
	function gray_to_color (context,x,y,paint_context) {

		// get image-data-Array from 0,0 to max of mouse movement area
		var image = context.getImageData(0,0,x,y)
		, image_data = image.data
		// get length of the array;
		, image_data_length = image_data.length
		//local ref to min/max opacity
		, min_op = options.min_opacity
		, max_op = options.max_opacity;

		// loop thru the area
		for(var i=0; i < image_data_length; i+=4){

			// [i] -> r, [i+1] -> g, [i+2] -> b, [i+3] -> alpha
			var gray_scale 		= image_data[i+3];

			// pointer for the gradient (total size is 256*4 -> gray_scale is between 1-256 so we have to multiply it by 4)
			var color_pointer	= gray_scale *4;

			// create the colored image data
			image_data[i]	= options.gradient[color_pointer];
			image_data[i+1]	= options.gradient[color_pointer+1];
			image_data[i+2]	= options.gradient[color_pointer+2];

			//dont let colors get non transparent, max_opacity is highest alpha_value
			image_data[i+3] 		= gray_scale > max_op ? max_op : Math.max(gray_scale, min_op);

		}
		//set new image-data as image.data
		image.data = image_data;
		//set data to heatmap_context
		paint_context.putImageData(image,0,0);
	}



	/*
	 * Calculate the grayshades for all the areas later to be painted in the heatmap
	 *
	 * @param x			- x_val of the center of a mouse-move event
	 * @param y			- y_val of the center of a mouse-move event
	 * @param count		- value of this event
	 * @param context	- the 2d-Context to draw grayshades on
	 * @return void
	 */
	function calculate_grayshades (x, y, count, context) {

		//get the radial gradient
		var radial_gradient = context.createRadialGradient(x,y,options.inner_radius,x,y,options.outer_radius)

		//get top and left position of the gradient
		, left = x-options.outer_radius
		, top = y-options.outer_radius

		//calculate alpha_value or set it to 0.1 (if value is max value that a point can have alpha is 1 for best coloring)
		, alpha_val = count/options.max_value||0.1;

		// set center of the radial gradient with according value
		radial_gradient.addColorStop(0, 'rgba(0,0,0,'+alpha_val+')');
		// let it fade to black, err transparent
		radial_gradient.addColorStop(1, 'rgba(0,0,0,0)');

		// set fillStyle to radial-gradient
		context.fillStyle = radial_gradient;
		// fill the rect
		context.fillRect(left,top,double_outer_radius,double_outer_radius);

	}

	/*
	 * Process the raw mouse-move event data and trigger gray-shade painting within
	 *
	 * @param data		- the mouse-move event data
	 * @param context	- the 2d-Context to draw grayshades on
	 * @return context	- the 2d-Context now filled with grayshades
	 */
	function process_data (data, context) {
		//local vars to stor x,y and val-value
		var x, y, val;
		//if centered design, translate (move 0,0) to new points
		if(options.canvas){
			context.translate(Math.round((options.canvas_width-options.canvas)/2),0);
		}

		//iterate over all data, increment by 3
		// [0] -> x, [1] -> y, [2] -> value
		for(var i =0, len = data.length; i< len; i+=3){
			//bitshift left by 3, because data is clustered before clientside
			x = data[i]<<3;
			//bitshift left by 3, because data is clustered before clientside
			y = data[i+1]<<3;
			val = data[i+2];
			//if x is in a valid area to paint, trigger painting gray-shades
			if((x > 0 && x < options.canvas) || !options.canvas){
				calculate_grayshades(x,y,val,context);
			}
		}
		//return the painted context
		return context;
	}



	/*
	 * Calculate the maximum Value of the Mouse-Move-Data-Array -> options.max_value
	 * Calculate the total Value of Mouse-Move-Data-Values -> options.total_val
	 *
	 * @param data		- the mouse-move event data
	 * @return void
	 */
	function calc_maximum (data) {
		//local variable to save current value
		var temp_val;

		//iterate over all points
		for(var i =0, len = data.length; i< len; i+=3){

			//save local reference
			temp_val = data[i+2];

			//just some addition of numbers
			options.total_val += temp_val;

			//if current val higher than last max_val save as new max_val
			if(temp_val > options.max_value ){
				options.max_value = temp_val;
			}
		}
	}



	/*
	 * Set size and some styles to the given Element
	 * used to fit canvas and iframes to each other
	 *
	 * @param width			- width the elemnt should get
	 * @param height		- height the elemnt should get
	 * @param element		- the element itself
	 * @param via_style		- set height/width via css or direct attributes //TRUE for css
	 * @param styles		- set additional CSS-styles // FALSE if styles should be set (yes its weird but convinient)
	 * @return void
	 */
	function set_size (width,height,element,via_style,styles) {

		//check if height/width should be set via css or as direct attributes
		if(via_style){
			element.style.width = width+"px";
			element.style.height = height+"px";
		}else{
			element.width = width;
			element.height = height;
		}

		//if canvas is set and styles shall be set add following styles
		if (options.canvas > 0 && !styles) {
			element.style.position = 'absolute';

			element.style.marginLeft = "-" + Math.round(width/2) + "px";

			element.style.top = "0";
			element.style.left = "50%";

		//if canvas is NOT set and styles shall be set add following styles
		}else if(!styles){
			element.style.position = 'absolute';
			element.style.top = "0";
			element.style.left = "0";
		}

	}

	/*
	 * Draw the clickmap on the clickmap canvas
	 *
	 * @param data			- mixed data of click-events
	 * @param context		- 2d-Context to be drawn on
	 * @return void
	 */
	function draw_click_map (data, context) {

		//save context, not really necessary though
		context.save();

		//if centered design, translate (move 0,0) to new points
		if(options.canvas){
			context.translate(Math.round((options.canvas_width-options.canvas)/2),0);
		}

		//set global Alpha of context to options.click_map_alpha
		context.globalAlpha = options.click_map_alpha;

		var x, y, density, event							// set some local variables used later
		,	color 				= options.click_color		// local reference to click-color object
		,	norm				= options.min_click_radius	// local reference to minimum click radius
		,	full				= Math.PI*2					// we want full circleeeessss AND WE WANT TAU!
		,	inner_radius		= 5;						// set inner circle (the white one)

		//iterate the points
		for(var i=0, len = data.length; i<len;i+=4){

			//get the event
			event = data[i];

			//check event type
			if(event === "l" || event === "c" || event === "r" ){
				//bitshift left by 3, because data is clustered before clientside
				x			= data[i+1]<<3;
				//bitshift left by 3, because data is clustered before clientside
				y			= data[i+2]<<3;
				//calculate outer radius based on min outer radius and squareroot of value (might be rethought)
				radius		= Math.sqrt(data[i+3])*norm;
				//if legitimate x-value continue
				if((x > 0 && x < options.canvas) || !options.canvas){

					//outer circle
					context.beginPath();
					context.strokeStyle = "rgba(0, 102, 204, 0.5)";
					context.lineWidth = (radius-inner_radius);
					context.arc(x, y, radius - (context.lineWidth/2), 0, full, true);
					context.closePath();
					context.stroke();

					//inner circle
					context.beginPath();
					context.strokeStyle = "rgba(255, 255, 255, 1)";
					context.lineWidth = 2;
					context.arc(x, y, inner_radius, 0, full, true);
					context.closePath();
					context.stroke();
				}
			}
		}
		//restore since we already saved
		context.restore();
	}



	function calc_steps (data) {

		var steps = options.attention_steps
		, att_vals = options.attention_step_values
		, pointer;

		for(var i=0, len = data.length; i<len; i+=3){
			pointer = Math.floor((data[i+1]<<3)/steps);
			if(!att_vals[pointer]){
				att_vals[pointer] = 0;
			}
			att_vals[pointer] += data[i+2];
		}
	};



	function color_attention (context) {

		var color, form_y, to_y
		,	att_vals	= options.attention_step_values
		,	max_val		= Math.max.apply(null,att_vals)
		,	max_color	= 255/max_val
		,	from_x		= 0
		,	to_x		= options.canvas_width
		,	steps		= options.attention_steps
		,	grad		= get_gradient(context, options.attention_gradient)
		,	att_op		= options.attention_opacity/255
		;

		for(var i=0, len = att_vals.length; i < len; i++){

			from_y	= i*steps;
			to_y	= steps;
			color	= Math.floor(max_color*att_vals[i]) * 4;

			context.fillStyle = "rgba("+grad[color]+","+grad[color+1]+","+grad[color+2]+","+att_op+")";
			context.fillRect(from_x,from_y,to_x,to_y);
		}
	};


	that.heatmap_paint = function (data) {

		if(data){
			options.data = data;
		}else{
			data = options.data;
		}

		options.canvas = data.page.canvas;

		if(options.canvas){
			document.getElementById(options.main_container).style.minWidth = options.canvas+"px";
		}else{
			document.getElementById(options.main_container).style.minWidth = "2000px";
		}

		calc_maximum(data.m);

		iframe = document.getElementById(options.iframe);
		set_size(options.canvas_width, options.canvas_height, iframe, true);

		var calc_canvas = that.create_canvas("calc_canvas", true)
		, heatmap_canvas = that.create_canvas("heatmap_canvas")
		, clickmap_canvas = that.create_canvas("clickmap_canvas")
		, attention_canvas = that.create_canvas("attention_canv")

		, calc_context = calc_canvas.getContext("2d")
		, heatmap_context = heatmap_canvas.getContext("2d")
		, clickmap_context = clickmap_canvas.getContext("2d")
		, attention_context = attention_canvas.getContext("2d");

		//get gradient before setting size, needs its own specifications
		options.gradient = get_gradient(calc_context);

		set_size(options.canvas_width, options.canvas_height, calc_canvas, false, true);
		set_size(options.canvas_width, options.canvas_height, heatmap_canvas, false);
		set_size(options.canvas_width, options.canvas_height, clickmap_canvas, false);
		set_size(options.canvas_width, options.canvas_height, attention_canvas, false);

		calc_context = process_data(data.m, calc_context);

		gray_to_color(calc_context, options.canvas_width, options.canvas_height, heatmap_context);

		window.location.search.indexOf("no_cm=1") === -1 && draw_click_map(data.r,clickmap_context);

		//sort array by y (largest first) value and set it to options.sorted_y
		//calc total amount of values and set it to options.total_val
		// set_sort_and_calc(data.m);

		calc_steps(data.m);

		color_attention(attention_context);


	};


})(sauberfant);
